﻿// WarframeAlert displays recent alerts from the WarframeAlerts Twitter feed.
// Copyright (C) 2013  Jeff Siebold aka NoonKnight
// Find updates of this program at <http://noonknight.com/WarframeAlert/>.
// Please send email to <noonknight@gmail.com> with WarframeAlert in the subject.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Deployment.Application;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Media;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml.Linq;
//using LinqToTwitter;

namespace WarframeAlert
{
	public partial class WaForm : Form
	{
		static private ulong? sinceID = null;
		static private LinqToTwitter.TwitterContext twitterCtx = null;
		enum RoundTimeTo { Seconds, Minutes, };
		static private RoundTimeTo roundTimeTo;

		private bool showTimerCounters = true;
		private int timer1_count = 1;
		private int timer2_count = 1;
		private const int SnapDist = 25;
		private const String twitterScreenName = "WarframeAlerts";
		private const String updatingFromTwitter = "Updating from Twitter...";
		private const String lastAlertBegan = "Last alert began {0} ago";
		private String registryKey = @"SOFTWARE\" + WarframeAlert.Name.Replace(" ", "");
		private String consumerKey = "";
		private String consumerSecret = "";
		private String warframeAlertKeys;

		private List<Alert> alerts = new List<Alert>();

		private class Alert : Object
		{
			public ulong statusID;
			public DateTime started;
			public int duration;
			public String location;
			public String detail;
			public int credits;
			public String item;
		}

		public WaForm()
		{
			/*\
			try
			{
				RegistryKey rk = Registry.CurrentUser.OpenSubKey(registryKey);
				warframeAlertKeys = (String)rk.GetValue("WarframeAlertKeys");
			}
			catch (NullReferenceException)
			{
				warframeAlertKeys = null;
			}
			if (warframeAlertKeys == null)
				warframeAlertKeys = "http://noonknight.com/WarframeAlert/WarframeAlertKeys.xml";
			\*/
			warframeAlertKeys = "http://noonknight.com/WarframeAlert/WarframeAlertKeys.xml";
			XDocument xmlDoc = XDocument.Load(warframeAlertKeys);
			consumerKey = xmlDoc.Descendants("TwitterAccessTokens")
				.Elements("ConsumerKey").Select(element => element.Value).First();
			consumerSecret = xmlDoc.Descendants("TwitterAccessTokens")
				.Elements("ConsumerSecret").Select(element => element.Value).First();

			InitializeComponent(); // VS UI design stuff

			foreach (ToolStripItem item in statusStrip1.Items)
				item.Text = "";
		}

		private void OnRoundTimeToChanged()
		{
			bool minutes = roundTimeTo == RoundTimeTo.Minutes;
			secondsToolStripMenuItem.Checked = !minutes;
			minutesToolStripMenuItem.Checked = minutes;

			var dataGridViewCellStyle = new DataGridViewCellStyle();
			dataGridViewCellStyle.Alignment = DataGridViewContentAlignment.TopCenter;
			dataGridViewCellStyle.Format = minutes ? "t" : "T";
			timeStart.DefaultCellStyle = dataGridViewCellStyle;
			timeEnd.DefaultCellStyle = dataGridViewCellStyle;
			SizeColumns();
		}

		private void Init_LinqToTwitter()
		{
			try
			{
				var auth = new LinqToTwitter.ApplicationOnlyAuthorizer
				{
					Credentials = new LinqToTwitter.InMemoryCredentials
					{
						ConsumerKey = consumerKey,
						ConsumerSecret = consumerSecret,
					}
				};
				auth.Authorize(); //auth.Invalidate();
				twitterCtx = new LinqToTwitter.TwitterContext(auth);
				return;
			}
			catch (LinqToTwitter.TwitterQueryException tqe)
			{
				MessageBox.Show(this, "Twitter: " + tqe.Message,
					WarframeAlert.Name, MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
			catch (Exception ex)
			{
				MessageBox.Show(this, ex.Message, // wrong LinqToTwitter.dll version?
					WarframeAlert.Name, MessageBoxButtons.OK, MessageBoxIcon.Error);
			}
			// an error occured check for clean-up
			if (twitterCtx != null)
			{
				twitterCtx.Dispose();
				twitterCtx = null;
			}
		}

		private void MainForm_Load(object sender, EventArgs e)
		{
			this.Text = WarframeAlert.Name;
			timer1.Interval = Properties.Settings.Default.MinsBetweenReads * 60 * 1000;
			LoadSettings();

			OnRoundTimeToChanged();
			UpdateStatus(updatingFromTwitter);
			backgroundWorker1.RunWorkerAsync();
		}

		private String FormatTimeSpan(TimeSpan ts)
		{
#if DEBUG
			TimeSpan rounded = ts.Round(TimeSpan.FromMilliseconds(1));
#else
			TimeSpan rounded = ts.Round(TimeSpan.FromSeconds(1));
#endif
			String formatted = "";
			if (rounded.Ticks < 0)
			{
				if (!Properties.Settings.Default.ShowNegativeTimeLeft)
					return "";
				formatted = "(";
			}

			if (rounded.Days != 0) formatted += Math.Abs(rounded.Days) + "d ";
			if (rounded.Hours != 0) formatted += Math.Abs(rounded.Hours) + "h ";
			if (rounded.Minutes != 0) formatted += Math.Abs(rounded.Minutes) + "m ";
			formatted += Math.Abs(rounded.Seconds) + "s";
#if DEBUG
			formatted += " " + Math.Abs(rounded.Milliseconds) + "ms";
#endif
			if (rounded.Ticks < 0) formatted += ")";
			return formatted;
		}

		private void AddNewAlerts(List<Alert> newAlerts)
		{
			if (newAlerts == null)
				return;

			SetTimerNow();
			for (int ndx = newAlerts.Count - 1; ndx >= 0; ndx--)
			{
				Alert alert = newAlerts[ndx];

				//DateTime started = alert.started.Round(GetRoundingInterval());
				DateTime timeEnds = alert.started.AddMinutes(alert.duration);
				TimeSpan timeLeft = timeEnds.Round(GetRoundingInterval()) - timerNow;

				object[] columns =
				{
					alert.started,
					timeEnds,
					timeLeft, // see also 
					alert.location,
					alert.detail,
					alert.credits,
					alert.item,
				};
				dataGridView1.Rows.Insert(0, columns);
				alerts.Add(alert);
			}

			if (dataGridView1.SortedColumn != null)
			{
				ListSortDirection direction =
					(dataGridView1.SortOrder == SortOrder.Ascending) ?
					ListSortDirection.Ascending : ListSortDirection.Descending;
				dataGridView1.Sort(dataGridView1.SortedColumn, direction);
			}

#if DEBUG
			this.dataGridView1.RowHeadersVisible = true;
			for (int ndx = 0; ndx < this.dataGridView1.Rows.Count; ndx++)
				this.dataGridView1.Rows[ndx].HeaderCell.Value = (ndx + 1).ToString();
#endif
			PlayAlertSound();
			SizeColumns();
			dataGridView1.ClearSelection();
			if (!timer2.Enabled)
				timer2.Start(); // update timeLeft every second
		}

		private void PlayAlertSound()
		{
			Stream stream;
			if (System.Diagnostics.Process.GetProcessesByName("Warframe.x64").Length > 0
			 || System.Diagnostics.Process.GetProcessesByName("Warframe").Length > 0)
				// sound ripped from http://translate.google.com/translate_tts?tl=en&q=attention%2C+there+is+a+new+warframe+alert
				// might have to be typed into the address bar by hand (or pasted)
				// b/c if it's not already cached you will likely get 404 overwise
				stream = Properties.Resources.NewAlert;
			else
				// sound file ripped from http://warframe.wikia.com/wiki/File:MOA_scream_1.ogg
				stream = Properties.Resources.MOA_scream_1;
			var soundPlayer = new SoundPlayer(stream);
			
			soundPlayer.Play();
		}

		private void SizeColumns()
		{
			this.dataGridView1.AutoResizeColumnHeadersHeight();
			this.dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
			int cnt = dataGridView1.Columns.Count;
			for (int ndx = 0; ndx < cnt - 1; ndx++)
				this.dataGridView1.Columns[ndx].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
			this.dataGridView1.Columns[cnt - 1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
			// datagrid has calculated it's widths so we can store them
			foreach (DataGridViewColumn col in dataGridView1.Columns)
			{
				// store autosized widths
				int w = col.Width;
				// remove autosizing
				col.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
				// set width to calculated by autosize
				col.Width = w;
			}
#if DEBUG
			this.dataGridView1.AutoResizeRowHeadersWidth(DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders);
#endif
		}

		private void doTimer1_Tick()
		{
			if (showTimerCounters)
			{
				timer1_count++;
				timer2_count = 1;
			}

			UpdateStatus(updatingFromTwitter);
			if (!backgroundWorker1.IsBusy)
				backgroundWorker1.RunWorkerAsync();
		}

		private void timer1_Tick(object sender, EventArgs e)
		{
			doTimer1_Tick();
		}

		private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
		{
			try
			{
				if (twitterCtx == null) // null?, is so init it
					Init_LinqToTwitter();
				if (twitterCtx == null) // still null?
				{
					e.Result = null;
					return;
				}
				var result = DoWork();
				if (result.Any())
					e.Result = result;
				else
					e.Result = null;
			}
			catch //(Exception ex)
			{
				e.Result = null;
			}
		}

		// by making this static, we ensure no access to an instance
		static private List<Alert> DoWork()
		{
			List<Alert> newAlerts;
			if (sinceID == null)
			{
				newAlerts = twitterCtx.Status.Where(tweet =>
					tweet.Type == LinqToTwitter.StatusType.User &&
					tweet.ScreenName == twitterScreenName &&
					tweet.Count == Properties.Settings.Default.NbrOfTweets)
					.Select(tweet => TweetToAlert(tweet)).ToList();
			}
			else
			{
				newAlerts = twitterCtx.Status.Where(tweet =>
					tweet.Type == LinqToTwitter.StatusType.User &&
					tweet.ScreenName == twitterScreenName &&
					tweet.SinceID == sinceID)
					.Select(tweet => TweetToAlert(tweet)).ToList();
			}
			if (newAlerts.Any())
				sinceID = newAlerts[0].statusID;
			return newAlerts;
		}

		static private TimeSpan GetRoundingInterval()
		{
			return (roundTimeTo == RoundTimeTo.Minutes) ? TimeSpan.FromMinutes(1) : TimeSpan.FromSeconds(1);
		}

		static private Alert TweetToAlert(LinqToTwitter.Status tweet)
		{
			Alert alert = new Alert();
			alert.statusID = ulong.Parse(tweet.StatusID);
			String[] fields = tweet.Text.Split(new String[] { " - " }, StringSplitOptions.RemoveEmptyEntries);
			alert.started = tweet.CreatedAt.ToLocalTime();
			if (fields.Length < 3)
			{
				alert.detail = fields[0];
				if (fields.Length > 1)
					alert.item = fields[1];
				return alert;
			}
			alert.duration = int.Parse(new String(fields[1].Where(c => char.IsDigit(c)).ToArray()));
			alert.credits = int.Parse(new String(fields[2].Where(c => char.IsDigit(c)).ToArray()));
			int colonNdx = fields[0].IndexOf(':');
			alert.location = fields[0].Substring(0, colonNdx);
			alert.detail = fields[0].Substring(colonNdx + 2);
			alert.item = "";
			if (fields.Length > 3)
			{
				if (fields[3].Trim() != "")
				{
					alert.item = fields[3].Trim();
					// CheckForNewItem(); // todo?
				}
			}
			return alert;
		}

		/// <summary>
		/// will be executed once backgroundWorker1 is completed
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void backgroundWorker1_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
		{
			SetTimerNow(); // save DateTime.Now for updating info
			if (e.Result == null || e.Error != null)
			{
				if (sinceID != null)
					UpdateStatus();
				return;
			}

			var newAlerts = (List<Alert>)e.Result;
			if (!dataGridView1.Visible)
			{
				dataGridView1.Visible = true;
				timer1.Start();
			}
			AddNewAlerts(newAlerts);
			UpdateStatus();
		}

		// use same time "Now" for entire method of timer2_Tick() & AlertIsOver()
		private DateTime timerNow;
		private void SetTimerNow()
		{
			timerNow = DateTime.Now.Round(TimeSpan.FromSeconds(1));
		}

		private void timer2_Tick(object sender, EventArgs e)
		{
			SetTimerNow();
			if (showTimerCounters)
				timer2_count++;
			foreach (Alert alert in alerts)
			{
				foreach (DataGridViewRow row in dataGridView1.Rows)
				{
					if ((DateTime)row.Cells["timeStart"].Value == alert.started)
					{
						int minutesLeft = alert.duration;
						DateTime timeEnds = alert.started.AddMinutes(minutesLeft);
						TimeSpan timeLeft = timeEnds.Round(GetRoundingInterval()) - timerNow;
						row.Cells["timeLeft"].Value = timeLeft;
						break;
					}
				}
			}
			UpdateStatus();
		}

		private bool AlertIsOver(Alert alert) // uses timerNow
		{
			int minutesLeft = alert.duration;
			DateTime timeEnds = alert.started.AddMinutes(minutesLeft);
			TimeSpan timeLeft = timeEnds.Round(GetRoundingInterval()) - timerNow;
			return timeLeft.Ticks < 1;
		}

		private void UpdateAlertStatus()
		{
			if (!alerts.Any())
			{
				alertStatus.Text = "";
				return;
			}
			alertStatus.Text = String.Format(lastAlertBegan,
				FormatTimeSpan(timerNow.Subtract(alerts.Last().started)));
		}

		private void UpdateActiveStatus()
		{
			//if (lastAlert == null)
			//{
				activeStatus.Text = "";
				return;
			//}
			//activeStatus.Text = activeAlerts.Count + " active alert";
			//if (activeAlerts.Count != 1)
			//	activeStatus.Text += "s";
		}

		private void UpdateNetworkStatus()
		{
			String timer1_status = timer1_count.ToString();
			if (!timer1.Enabled)
				timer1_status += "*";
			String timer2_status = Math.Max(0, Properties.Settings.Default.MinsBetweenReads * 60 - timer2_count).ToString();
			if (!timer2.Enabled)
				timer2_status += "*";
			networkStatus.Text = timer1_status + " / " + timer2_status;
		}

		private void UpdateStatus(String alertStatusOverride = "")
		{
			//if (!statusStrip1.Visible)
			//	return;
			if (alertStatusOverride != "")
			{
				alertStatus.Text = alertStatusOverride;
				activeStatus.Text = "";
				networkStatus.Text = "";
			}
			else
			{
				UpdateAlertStatus();
				UpdateActiveStatus();
				UpdateNetworkStatus();
			}
		}

		private void LoadSettings()
		{
			bool flag;

			String fontName = Properties.Settings.Default.FontName;
			float fontSize = Properties.Settings.Default.FontSize;
			String fontStyle = Properties.Settings.Default.FontStyle;
			SetFont(fontName, fontSize, fontStyle);

			roundTimeTo = (RoundTimeTo)Enum.Parse(typeof(RoundTimeTo),
				Properties.Settings.Default.RoundTimeTo);
			//OnRoundTimeToChanged(); moved to MainForm_Load()

			flag = Properties.Settings.Default.ShowStatusBar;
			showStatusBarToolStripMenuItem.Checked = flag;
			statusStrip1.Visible = flag;

			flag = Properties.Settings.Default.ShowNegativeTimeLeft;
			negativeToolStripMenuItem.Checked = flag;

			WindowPlacement.SetPlacement(this.Handle, Properties.Settings.Default.WndPlacement);
		}

		private void SetFont(String fontName = "", float fontSize = 0.0f, String fontStyle = "")
		{
			if (fontName == "")
				if (Properties.Settings.Default.FontName == "")
					fontName = this.Font.Name;
				else
					fontName = Properties.Settings.Default.FontName;
			if (fontSize < 1)
				if (Properties.Settings.Default.FontSize < 1)
					fontSize = this.Font.Size;
				else
					fontSize = Properties.Settings.Default.FontSize;
			if (fontStyle == "")
				if (Properties.Settings.Default.FontStyle == "")
					fontStyle = this.Font.Style.ToString();
				else
					fontStyle = Properties.Settings.Default.FontStyle;
			TypeConverter fontStyleConverter = TypeDescriptor.GetConverter(typeof(FontStyle));
			var _fontStyle = (FontStyle)fontStyleConverter.ConvertFromString(fontStyle);
			this.Font = new Font(fontName, fontSize, _fontStyle);
			statusStrip1.Font = this.Font;
		}

		private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			Properties.Settings.Default.WndPlacement = WindowPlacement.GetPlacement(this.Handle);
			Properties.Settings.Default.Save();
		}

		private Boolean DoSnap(int pos, int edge)
		{
			return Math.Abs(pos - edge) < SnapDist;
		}

		private void MainForm_ResizeEnd(object sender, EventArgs e)
		{
			Screen scn = Screen.FromPoint(this.Location);
			if (DoSnap(this.Left, scn.WorkingArea.Left)) this.Left = scn.WorkingArea.Left;
			if (DoSnap(this.Top, scn.WorkingArea.Top)) this.Top = scn.WorkingArea.Top;
			if (DoSnap(scn.WorkingArea.Right, this.Right)) this.Left = scn.WorkingArea.Right - this.Width;
			if (DoSnap(scn.WorkingArea.Bottom, this.Bottom)) this.Top = scn.WorkingArea.Bottom - this.Height;
		}

		private void fontToolStripMenuItem_Click(object sender, EventArgs e)
		{
			FontDialog fntDlg = new FontDialog();
			fntDlg.Font = this.Font;
			if (fntDlg.ShowDialog() == DialogResult.OK)
			{
				Properties.Settings.Default.FontName = fntDlg.Font.Name;
				Properties.Settings.Default.FontSize = fntDlg.Font.Size;
				Properties.Settings.Default.FontStyle = fntDlg.Font.Style.ToString();
				Properties.Settings.Default.Save();
				SetFont();
				SizeColumns();
			}
		}

		private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
		{
			String name = WarframeAlert.Name;
#if DEBUG
			String compiledOn = RetrieveLinkerTimestamp().ToString();
#else
			String compiledOn = RetrieveLinkerTimestamp().ToShortDateString();
#endif
			String version;
			try
			{
				if (ApplicationDeployment.IsNetworkDeployed)
					version = ApplicationDeployment.CurrentDeployment.
						CurrentVersion.ToString();
				else
					version = "is not known";
			}
			catch
			{
				version = "error,";
			}

			String about = String.Format(
				"{0} version {1}\n" +
				"Compiled on {2}\n" +
				"Written by Jeff Siebold aka NoonKnight",
				name, version, compiledOn);
			MessageBox.Show(this, about, name, MessageBoxButtons.OK, MessageBoxIcon.None);
		}

		private void exitToolStripMenuItem_Click(object sender, EventArgs e)
		{
			this.Close();
		}

		private void columnWidthsToolStripMenuItem_Click(object sender, EventArgs e)
		{
			SizeColumns();
		}

		private DateTime RetrieveLinkerTimestamp()
		{
			String filePath = System.Reflection.Assembly.GetCallingAssembly().Location;
			const int c_PeHeaderOffset = 60;
			const int c_LinkerTimestampOffset = 8;
			byte[] b = new byte[2048];
			Stream s = null;

			try
			{
				s = new FileStream(filePath, FileMode.Open, System.IO.FileAccess.Read);
				s.Read(b, 0, 2048);
			}
			finally
			{
				if (s != null)
				{
					s.Close();
				}
			}

			int i = BitConverter.ToInt32(b, c_PeHeaderOffset);
			int secondsSince1970 = BitConverter.ToInt32(b, i + c_LinkerTimestampOffset);
			DateTime dt = new DateTime(1970, 1, 1, 0, 0, 0);
			dt = dt.AddSeconds(secondsSince1970);
			dt = dt.AddHours(TimeZone.CurrentTimeZone.GetUtcOffset(dt).Hours);
			return dt;
		}

		private void showStatusBarToolStripMenuItem_Click(object sender, EventArgs e)
		{
			var toolStripMenuItem = sender as ToolStripMenuItem;
			if (toolStripMenuItem == null) throw new ArgumentException();
			bool flag = !toolStripMenuItem.Checked;
			toolStripMenuItem.Checked = flag;

			statusStrip1.Visible = flag;
			Properties.Settings.Default.ShowStatusBar = flag;
			Properties.Settings.Default.Save();
		}

		private int? GetInteger(int value, String mess, String title)
		{
			Form form = new Form();
			form.Font = this.Font;
			form.FormBorderStyle = FormBorderStyle.FixedDialog;
			form.Text = title;

			var label = new Label();
			label.AutoSize = true;
			label.Text = mess;
			form.Controls.Add(label);

			var textBox = new TextBox();
			textBox.Text = value.ToString();
			form.Controls.Add(textBox);

			var okay = new Button();
			okay.DialogResult = DialogResult.OK;
			okay.Text = "Okay";
			form.Controls.Add(okay);

			var cancel = new Button();
			cancel.DialogResult = DialogResult.Cancel;
			cancel.AutoSize = true;
			cancel.Text = "Cancel";
			form.Controls.Add(cancel);

			var margin = new Size(
				form.Width - form.ClientRectangle.Width,
				(form.Height - form.ClientRectangle.Height) / 2);

			form.Load += delegate
			{
				// move/resize the controls
				//label.BorderStyle = BorderStyle.FixedSingle; // for debugging
				label.Location = new Point(margin.Width, margin.Height);
				int width = label.Width;
				label.AutoSize = false;
				label.Size = new Size(width, textBox.Height);
				label.TextAlign = ContentAlignment.MiddleLeft;
				textBox.Location = new Point(label.Right, label.Top);
				width = (int)form.CreateGraphics().MeasureString("  " + int.MaxValue.ToString(), form.Font).Width;
				textBox.Size = new Size(width, textBox.Height);

				// set start location of the form
				form.Location = new Point(
					form.Left + margin.Width * 2,
					form.Top + margin.Height * 2);

				// resize the form to fit the controls
				form.Size = new Size(
					margin.Width * 3 + label.Width + textBox.Size.Width,
					margin.Height * 5 + textBox.Height + cancel.Height);

				// +------------------------------+
				// |   +---------+   +--------+   |
				// |<=>| Connect |<=>| Cancel |<=>| where <=> is buttonMargin
				// |   +---------+   +--------+   |
				// +------------------------------+
				var buttonMargin = (form.ClientRectangle.Width - cancel.Width * 2) / 3;
				cancel.Location = new Point(
					form.ClientRectangle.Width - cancel.Width - buttonMargin,
					form.ClientRectangle.Height - cancel.Height - margin.Height);
				okay.Size = new Size(cancel.Width, cancel.Height); // assume cancel is larger
				okay.Location = new Point(buttonMargin, cancel.Top);
			};

			// get the result to be returned
			int? result = null;
			var dlgResult = form.ShowDialog();
			if (dlgResult == DialogResult.OK)
			{
				int tryParse;
				if (Int32.TryParse(textBox.Text, out tryParse))
					result = tryParse;
			}

			// clean-up
			form.Dispose();
			form = null;

			return result;
		}

		private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
		{
			if (e.ColumnIndex == dataGridView1.Columns["timeLeft"].Index)
			{
				TimeSpan timeLeft;
				TimeSpan.TryParse(e.Value.ToString(), out timeLeft);
				e.Value = FormatTimeSpan(timeLeft);
				e.FormattingApplied = true;
			}
		}

		private String GetToolStripMenuItemText(Object sender)
		{
			var toolStripMenuItem = sender as ToolStripMenuItem;
			if (toolStripMenuItem == null) throw new ArgumentException();
			return toolStripMenuItem.Text.Replace("&", "").Replace("...", "");
		}

		private String GetSenderTree(Object sender)
		{
			var toolStripMenuItem = sender as ToolStripMenuItem;
			if (toolStripMenuItem == null) return "unknown";

			var text = GetToolStripMenuItemText(toolStripMenuItem);
			String parentText;
			if (toolStripMenuItem.OwnerItem == null)
				// just return the Text of the TopLevelControl (Form) and end any recursing
				parentText = toolStripMenuItem.Owner.TopLevelControl.Text;
			else
				// recurse if there is another ToolStripMenuItem
				parentText = GetSenderTree(toolStripMenuItem.OwnerItem);
			return text + " · " + parentText;
		}

		private void numberToReadToolStripMenuItem_Click(object sender, EventArgs e)
		{
			int? nbrOfTweets = GetInteger(
				Properties.Settings.Default.NbrOfTweets,
				GetToolStripMenuItemText(sender) + " (1 - 99 tweets)",
				GetSenderTree(sender));
			if (nbrOfTweets == null)
				return;
			if (nbrOfTweets.Value < 1 || nbrOfTweets.Value > 99)
				return;
			Properties.Settings.Default.NbrOfTweets = nbrOfTweets.Value;
			Properties.Settings.Default.Save();

			doTimer1_Tick(); // forces a refresh
		}

		private void refreshNowToolStripMenuItem_Click(object sender, EventArgs e)
		{
			doTimer1_Tick(); // forces a refresh
		}

		private void frequencyOfRefreshToolStripMenuItem_Click(object sender, EventArgs e)
		{
			int? minsBetweenReads = GetInteger(
				Properties.Settings.Default.MinsBetweenReads,
				GetToolStripMenuItemText(sender) + " (1 - 99 minutes)",
				GetSenderTree(sender));
			if (minsBetweenReads == null)
				return;
			if (minsBetweenReads.Value < 1 || minsBetweenReads.Value > 99)
				return;
			Properties.Settings.Default.MinsBetweenReads = minsBetweenReads.Value;
			Properties.Settings.Default.Save();

			timer1.Stop(); // to change the interval, stop the timer
			// convert minutes to milliseconds (60 seconds/minute * 1000 milliseconds/second)
			timer1.Interval = minsBetweenReads.Value * 60 * 1000;
			timer1.Start(); // don't forget to restart it

			doTimer1_Tick(); // forces a refresh
		}

		private void negativeToolStripMenuItem_Click(object sender, EventArgs e)
		{
			var toolStripMenuItem = sender as ToolStripMenuItem;
			if (toolStripMenuItem == null) throw new ArgumentException();
			bool flag = !toolStripMenuItem.Checked;
			toolStripMenuItem.Checked = flag;

			Properties.Settings.Default.ShowNegativeTimeLeft = flag;
			Properties.Settings.Default.Save();
		}

		private void roundTimeToToolStripMenuItem_Click(object sender, EventArgs e)
		{
			var toolStripMenuItem = sender as ToolStripMenuItem;
			if (toolStripMenuItem == null) throw new ArgumentException();
			bool flag = !toolStripMenuItem.Checked;
			toolStripMenuItem.Checked = flag;

			var text = GetToolStripMenuItemText(toolStripMenuItem);
			roundTimeTo = (RoundTimeTo)Enum.Parse(typeof(RoundTimeTo), text);
			OnRoundTimeToChanged();
			Properties.Settings.Default.RoundTimeTo = text;
			Properties.Settings.Default.Save();
		}

		private void openConfigFolderToolStripMenuItem_Click(object sender, EventArgs e)
		{
			var config = System.Configuration.ConfigurationManager.OpenExeConfiguration(
				System.Configuration.ConfigurationUserLevel.PerUserRoamingAndLocal);
			var filePath = config.FilePath;
#if DEBUG
			MessageBox.Show(this, "Config File: " + filePath,
				WarframeAlert.Name, MessageBoxButtons.OK, MessageBoxIcon.None);
#endif
			var directory = Path.GetDirectoryName(filePath);
#if DEBUG
			MessageBox.Show(this, "Config folder: " + directory,
				WarframeAlert.Name, MessageBoxButtons.OK, MessageBoxIcon.None);
#endif
			if (!Directory.Exists(directory))
				Properties.Settings.Default.Save();
			if (Directory.Exists(directory))
				System.Diagnostics.Process.Start(directory);
		}

		[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
		protected override void WndProc(ref Message m)
		{
			if (m.Msg == WarframeAlert.WM_SHOWME)
				ShowMe();

			base.WndProc(ref m);
		}

		private void ShowMe()
		{
			if (WindowState == FormWindowState.Minimized)
				WindowState = FormWindowState.Normal;

			// activate the form
			Activate();

			// activate any visible owned modal dialog (Form or not), if any
			ModalWindowUtil.ActivateWindow(ModalWindowUtil.GetModalWindow(Handle));
		}

		private class ModalWindowUtil
		{
			[DllImport("user32")]
			private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

			[DllImport("user32")]
			private static extern IntPtr SetActiveWindow(IntPtr hWnd);

			[DllImport("user32")]
			private static extern bool IsWindowVisible(IntPtr hWnd);

			[DllImport("kernel32")]
			private static extern int GetCurrentThreadId();

			[DllImport("user32")]
			private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);
			private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);

			private const int GW_OWNER = 4;
			private int _maxOwnershipLevel;
			private IntPtr _maxOwnershipHandle;

			private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
			{
				int level = 1;
				if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
				{
					if (level > _maxOwnershipLevel)
					{
						_maxOwnershipHandle = hwnd;
						_maxOwnershipLevel = level;
					}
				}
				return true;
			}

			private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
			{
				IntPtr o = GetWindow(hwnd, GW_OWNER);
				if (o == IntPtr.Zero)
					return false;

				if (o == owner)
					return true;

				level++;
				return IsOwned(owner, o, ref level);
			}

			public static void ActivateWindow(IntPtr hwnd)
			{
				if (hwnd != IntPtr.Zero)
					SetActiveWindow(hwnd);
			}

			public static IntPtr GetModalWindow(IntPtr owner)
			{
				ModalWindowUtil util = new ModalWindowUtil();
				EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
				return util._maxOwnershipHandle; // may be IntPtr.Zero
			}
		}
	}
}
